package com.topvision.common.token;

import com.google.common.collect.Maps;
import com.topvision.common.exception.BusinessException;
import com.topvision.common.security.CustomAuthenticationToken;
import com.topvision.common.security.UserInfo;
import com.topvision.config.TopvisionProperties;
import com.topvision.utils.text.JsonMapper;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author shengwm
 * @version V0.0.1
 * @Title: TokenProvider
 * @Package com.seed.security.auth.jwt
 * @Description: JWT令牌管理
 * @date 2017/6/16
 */
@Component
@Slf4j
public class TokenProvider {
    @Inject
    private TopvisionProperties topvisionProperties;

    /**
     * 创建AccessToken
     *
     * @param authentication
     * @param rememberMe
     * @return
     */
    public Map<String, Object> createAccessToken(Authentication authentication, Boolean rememberMe) {
        AccessToken accessToken = this.cretaeAccessToken(authentication, rememberMe);
        AccessToken refreshToken = this.createRefreshToken(authentication);
        Map<String, Object> tokenMap = Maps.newHashMap();
        tokenMap.put("access_token", accessToken.getToken());
        tokenMap.put("refresh_token", refreshToken.getToken());
        tokenMap.put("token_type", "bearer");
        tokenMap.put("expires_in", accessToken.getClaims().getExpiration().getTime());
        tokenMap.put("scope", accessToken.getClaims().get("scopes"));
        return tokenMap;
    }

    /**
     * 创建AccessToken
     *
     * @param userInfo
     * @param rememberMe
     * @return
     */
    public Map<String, Object> createAccessToken(UserInfo userInfo, Boolean rememberMe) {
        AccessToken accessToken = this.cretaeAccessToken(userInfo, rememberMe);
        AccessToken refreshToken = this.createRefreshToken(userInfo);
        Map<String, Object> tokenMap = Maps.newHashMap();
        tokenMap.put("access_token", accessToken.getToken());
        tokenMap.put("refresh_token", refreshToken.getToken());
        tokenMap.put("token_type", "bearer");
        tokenMap.put("expires_in", accessToken.getClaims().getExpiration().getTime());
        tokenMap.put("scope", accessToken.getClaims().get("scopes"));
        return tokenMap;
    }


    /**
     * 创建AccessToken
     *
     * @param authentication 用户授权信息
     * @param rememberMe     是否记住我
     * @return
     */
    public AccessToken cretaeAccessToken(Authentication authentication, Boolean rememberMe) {
     /*   User user =(User)authentication.getPrincipal();
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(user.getUsername());
        userInfo.setAuthorities(user.getAuthorities());
        userInfo.setPassword(user.getPassword());
        userInfo.setEnabled(true);*/
        UserInfo userInfo = (UserInfo) authentication.getPrincipal();
        return cretaeAccessToken(userInfo, rememberMe);
    }

    /**
     * 创建JWT令牌
     *
     * @param userInfo
     * @param rememberMe
     * @return
     */
    public AccessToken cretaeAccessToken(UserInfo userInfo, Boolean rememberMe) {
        if (StringUtils.isBlank(userInfo.getUsername()))
            throw new IllegalArgumentException("没有用户名不能创建JWT令牌");

//        if (userInfo.getAuthorities() == null || userInfo.getAuthorities().isEmpty())
//            throw new IllegalArgumentException("用户没有任何权限，请联系管理员授权！");

        Claims claims = Jwts.claims().setSubject(userInfo.getUsername());
        claims.put("scopes", userInfo.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
        claims.put("info", JsonMapper.defaultMapper().toJson(userInfo));

        DateTime currentTime = new DateTime();
        Date settingTime;
        if (rememberMe) {
            // 记住我时间
            settingTime = currentTime.plusSeconds(topvisionProperties.getSecurity().getAuthentication().getJwt().getRememberMeExpTime()).toDate();
        } else {
            // 默认时间
            settingTime = currentTime.plusSeconds(topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenExpirationTime()).toDate();
        }
        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuer(topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenIssuer())
                .setIssuedAt(currentTime.toDate())
                .setExpiration(settingTime)
                .signWith(SignatureAlgorithm.HS512, topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenSigningKey())
                .compact();

        return new AccessToken(token, claims);
    }

    /**
     * 刷新AccessToken
     *
     * @param authentication
     * @return
     */
    private AccessToken createRefreshToken(Authentication authentication) {
        UserInfo userInfo = (UserInfo) authentication.getPrincipal();


      /*  User user =(User)authentication.getPrincipal();
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(user.getUsername());
        userInfo.setAuthorities(user.getAuthorities());
        userInfo.setPassword(user.getPassword());
        userInfo.setEnabled(true);*/
        return createRefreshToken(userInfo);
    }

    /**
     * 创建刷新令牌
     *
     * @param userInfo
     * @return
     */
    private AccessToken createRefreshToken(UserInfo userInfo) {
        if (StringUtils.isBlank(userInfo.getUsername())) {
            throw new IllegalArgumentException("没有用户名不能创建JWT令牌");
        }

        DateTime currentTime = new DateTime();

        Claims claims = Jwts.claims().setSubject(userInfo.getUsername());
        claims.put("scopes", Arrays.asList("refresh_token"));
        claims.put("info", JsonMapper.defaultMapper().toJson(userInfo));

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuer(topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenIssuer())
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(currentTime.toDate())
                .setExpiration(currentTime.plusMinutes(topvisionProperties.getSecurity().getAuthentication().getJwt().getRefreshTokenExpTime()).toDate())
                .signWith(SignatureAlgorithm.HS512, topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenSigningKey())
                .compact();

        return new AccessToken(token, claims);
    }

    /**
     * 根据JWT令牌获取授权信息
     *
     * @param token
     * @return
     */
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenSigningKey())
                .parseClaimsJws(token)
                .getBody();

        List<GrantedAuthority> authorities = ((List<String>) claims.get("scopes")).stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());

        UserInfo userInfo = JsonMapper.defaultMapper().fromJson(claims.get("info").toString(), UserInfo.class);

        UserInfo principal = UserInfo.create(userInfo.getId(), userInfo.getUsername(),userInfo.getPassword(), true, userInfo.getName(), userInfo.getMobile(), userInfo.getEmail(), authorities);

        return new CustomAuthenticationToken(principal, authorities);
    }


    /**
     * 验证JWT令牌
     *
     * @param authToken
     * @return
     */
    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(topvisionProperties.getSecurity().getAuthentication().getJwt().getTokenSigningKey()).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            log.info("JWT令牌签名无效：{}", e.getMessage());
            throw new BusinessException("invalid_token_signature", "JWT令牌签名无效");
        } catch (MalformedJwtException e) {
            log.info("JWT令牌无效: {}", e.getMessage());
            throw new BusinessException("invalid_token", "JWT令牌无效");
        } catch (ExpiredJwtException e) {
            log.info("JWT令牌过期: {}", e.getMessage());
            throw new BusinessException("expire_token", "JWT令牌过期");
        } catch (UnsupportedJwtException e) {
            log.info("不支持的JWT令牌: {}", e.getMessage());
            throw new BusinessException("nnsupport_token", "不支持的JWT令牌");
        } catch (IllegalArgumentException e) {
            log.info("应用处理中JWT无效：{}", e.getMessage());
            throw new BusinessException("invalid_token", "JWT令牌无效");
        }
    }
}
